package com.junyi.window;

import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;

import java.time.LocalTime;
import java.util.concurrent.TimeUnit;

/**
 * 固定时间窗口限流算法，该算法比较简单，但是经常会发生 QPS 超过阈值的情况
 * 典型的场景是，我们设置 QPS 为 10，在1秒的前500ms无请求，后500ms有10个请求。然后再下一秒的前500ms有10个请求，后500ms无请求
 * 请求都是可以正常请求的，但是我们会发现 QPS 为 20。
 * @time: 2022/7/22 10:56
 * @version: 1.0
 * @author: junyi Xu
 * @description:
 */
@Slf4j
// @Component
public class FixWindow implements ApplicationRunner {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    public static final String LIMIT_PREFIX = "limit:";

    public static final String EXPIRE = "60";

    public static final String QPS = "4";    // 每秒允许多少个请求

    @Override
    public void run(ApplicationArguments args) throws Exception {
        test();
    }



    public void test() throws InterruptedException {
        log.info("固定窗口限流算法");
        for (int i = 0; i < 20; i++) {
            log.info("当前时间: {}, 结果：{}", LocalTime.now(), getAndTry("test"));
            TimeUnit.MILLISECONDS.sleep(200);
        }
    }

    private Boolean getAndTry(String key) {
        // 当前的秒数为Redis Key
        key = LIMIT_PREFIX + key + System.currentTimeMillis() / 1000;
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setResultType(Long.class);
        script.setScriptSource(new ResourceScriptSource(new ClassPathResource("fix-window.lua")));
        return stringRedisTemplate.execute(script, Lists.newArrayList(key), EXPIRE, QPS) == 1;
    }


}
